package com.mxx.common.conv;

import com.mxx.common.utils.Strings;
import lombok.SneakyThrows;
import org.springframework.beans.BeanWrapper;
import org.springframework.beans.BeanWrapperImpl;

import java.beans.PropertyDescriptor;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.BiConsumer;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * Bean 工具类
 *
 * @author ruoyi
 */
public class BeanUtil extends org.springframework.beans.BeanUtils implements ConvertValue {
    /**
     * Bean方法名中属性名开始的下标
     */
    private static final int BEAN_METHOD_PROP_INDEX = 3;

    private static final Map<Class, Object> SINGLETON_MAP = new ConcurrentHashMap<>();
    /**
     * 匹配getter方法的正则表达式
     */
    private static final Pattern GET_PATTERN = Pattern.compile("get(\\p{javaUpperCase}\\w*)");

    /**
     * 匹配setter方法的正则表达式
     */
    private static final Pattern SET_PATTERN = Pattern.compile("set(\\p{javaUpperCase}\\w*)");

    private static final BeanWrapper beanWrapper;

    static {
        beanWrapper = new BeanWrapperImpl();
        beanWrapper.registerCustomEditor(LocalDate.class, new LocalDateEditor());
        beanWrapper.registerCustomEditor(LocalDateTime.class, new LocalDateTimeEditor());
        beanWrapper.registerCustomEditor(String.class, new StringLocalDateTimeEditor());
    }


    /**
     * 替换数据
     *
     * @param source
     * @param target
     * @param columns
     * @return
     */
    public static void replace(Object source, Object target, String... columns) {
        Set<String> nullProperties = BeanUtil.filterNull(source);
        nullProperties.addAll(Arrays.asList(columns));
        BeanUtil.copyProperties(source, target, nullProperties.toArray(new String[0]));
    }

    /**
     * 获取空值字段
     *
     * @param source
     * @return
     */
    public static Set<String> filterNull(Object source) {
        Set<String> nullProperties = new TreeSet<>();
        BeanUtil.eachObject(source, (k, v) -> {
            if (Strings.isEmpty(v)) {
                nullProperties.add(k);
            }
        });

        return nullProperties;
    }

    @Override
    public <T> T convertValue(Object val, Class<T> classes) {
        return beanWrapper.convertIfNecessary(val, classes);
    }

    public static BeanWrapper getBeanWrapper() {
        return beanWrapper;
    }

    /**
     * 遍历实体类中的字符
     *
     * @param source
     * @param consumer
     */
    public static void eachObject(Object source, BiConsumer<String, Object> consumer) {
        BeanWrapper wrapper = new BeanWrapperImpl(source);
        PropertyDescriptor[] propertyDescriptors = wrapper.getPropertyDescriptors();
        for (PropertyDescriptor propertyDescriptor : propertyDescriptors) {
            String fieldName = propertyDescriptor.getName();
            if (fieldName.equals("class")) {
                continue;
            }
            Object fieldVal = wrapper.getPropertyValue(fieldName);
            consumer.accept(fieldName, fieldVal);
        }
    }

    /**
     * 获取对象的setter方法。
     *
     * @param obj 对象
     * @return 对象的setter方法列表
     */
    public static List<Method> getSetterMethods(Object obj) {
        List<Method> setterMethods = new ArrayList<Method>();
        Method[] methods = obj.getClass().getMethods();
        for (Method method : methods) {
            Matcher m = SET_PATTERN.matcher(method.getName());
            if (m.matches() && (method.getParameterTypes().length == 1)) {
                setterMethods.add(method);
            }
        }
        return setterMethods;
    }

    /**
     * 获取对象的getter方法。
     *
     * @param obj 对象
     * @return 对象的getter方法列表
     */

    public static List<Method> getGetterMethods(Object obj) {
        List<Method> getterMethods = new ArrayList<Method>();
        Method[] methods = obj.getClass().getMethods();
        for (Method method : methods) {
            Matcher m = GET_PATTERN.matcher(method.getName());
            if (m.matches() && (method.getParameterTypes().length == 0)) {
                getterMethods.add(method);
            }
        }
        return getterMethods;
    }

    @SneakyThrows
    public static <T> T singleton(Class<T> classes, Object[] args) {
        Object data = SINGLETON_MAP.get(classes);
        if (data == null) {
            synchronized (classes) {
                data = SINGLETON_MAP.get(classes);
                if (data == null) {
                    if (args.length == 0) {
                        data = classes.newInstance();
                    } else {
                        Constructor[] constructors = classes.getConstructors();
                        for (Constructor constructor : constructors) {
                            boolean equal = true;
                            Class[] paramTypes = constructor.getParameterTypes();
                            if (paramTypes.length == args.length) {
                                for (int i = 0; i < paramTypes.length; i++) {
                                    Object param = args[i];
                                    if (param != null && param.getClass().equals(paramTypes[i])) {
                                        equal = false;
                                        break;
                                    }
                                }
                            }
                            if (equal) {
                                data = constructor.newInstance(args);
                            }
                        }
                    }
                    if (data != null) {
                        SINGLETON_MAP.put(classes, data);
                    } else {
                        throw new IllegalArgumentException("No available constructors found");
                    }
                }
            }
        }
        return (T) data;
    }
}
